
private{
	import utils.Singleton;
	import Screen;

	import std.stdio:writefln;
	import std.c.math:sqrt;
	import std.c.stdlib:malloc;

	import derelict.opengl.gl;
	import derelict.opengl.glu;
	import derelict.opengl.extension.ext.framebuffer_object;

	import cgGL;
	import std.string;
	import derelict.sdl.sdl;
}

const GLenum GL_RGBA32F_ARB	= 0x8814;


float* dataX;
float* dataY;


int texSize;
GLuint yTexID[2];
GLuint xTexID;
GLuint aTexID;

int writeTex = 0;
int readTex = 1;

GLuint fb;

CGcontext	myCgContext;
CGprofile   myCgFragmentProfile;
CGprogram   myCgFragmentProgram;
CGprogram   myPiFragmentProgram;
CGparameter yParam, xParam;

char[] myFragmentProgramFileName = "cg\\C2E1v_green.cg";
char[] myFragmentProgramName = "perlin";

int numIterations = 1;

void init(){
    DerelictGL.load();
    DerelictGLU.load();
}

void cleanup(){
	writefln("STFU");
}

void checkForCgError(char[] situation){
	CGerror error;

	char* string = cgGetLastErrorString(&error);
	if(error != CGerror.CG_NO_ERROR) {
	    writefln("%s: %s", situation, toString(string) );
		if(error == CGerror.CG_COMPILER_ERROR)
			writefln(toString(cgGetLastListing(myCgContext)));
	}

}

void initFBO() {
	writefln("GL_EXT_framebuffer_object: ",EXTFramebufferObject.load("GL_EXT_framebuffer_object"));
	assert(glGenFramebuffersEXT !is null);
    glGenFramebuffersEXT(1, &fb); 
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluOrtho2D(0.0, texSize, 0.0, texSize);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, texSize, texSize);
}

void setupTexture (GLuint texID) {
	glBindTexture(GL_TEXTURE_2D,texID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F_ARB, texSize, texSize, 0, GL_RGBA, GL_FLOAT, null);
}

void transferToTexture (float* data, GLuint texID) {
	glBindTexture(GL_TEXTURE_2D, texID);
	glTexSubImage2D(GL_TEXTURE_2D,0,0,0,texSize,texSize,GL_RGBA,GL_FLOAT,data);
}

void transferFromTexture(float* data) {
    glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,data);
}


void createTextures () {
    glGenTextures (2, yTexID);
    glGenTextures (1, &xTexID);

    setupTexture (yTexID[readTex]);
	transferToTexture(dataY,yTexID[readTex]);
    setupTexture (yTexID[writeTex]);
	transferToTexture(dataY,yTexID[writeTex]);
    setupTexture (xTexID);
	transferToTexture(dataX,xTexID);

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

void initMyCgProgram(){
	myCgContext = cgCreateContext();
	checkForCgError("creating context");

	myCgFragmentProfile = cgGLGetLatestProfile(CGGLenum.CG_GL_FRAGMENT);
	cgGLSetOptimalOptions(myCgFragmentProfile);
	checkForCgError("selecting vertex profile");
	writefln(cast(int)myCgFragmentProfile);

	myCgFragmentProgram = 
		cgCreateProgramFromFile(
				myCgContext,
				CGenum.CG_SOURCE,
				toStringz(myFragmentProgramFileName),
				myCgFragmentProfile,
				toStringz(myFragmentProgramName),
				null);
	checkForCgError("creating vertex program from file");
	cgGLLoadProgram(myCgFragmentProgram);
	checkForCgError("loading vertex program");
	writefln("Loaded.");
	yParam = cgGetNamedParameter(myCgFragmentProgram, toStringz("textureY"));
	xParam = cgGetNamedParameter(myCgFragmentProgram, toStringz("textureX"));
	checkForCgError("cgGetNamedParameter");
}

void performComputation() {

    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, yTexID[writeTex], 0);//write
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, yTexID[readTex], 0);//read

	cgGLEnableProfile(myCgFragmentProfile);
	checkForCgError("cgGLEnableProfile");	

	cgGLBindProgram(myCgFragmentProgram);
	checkForCgError("cgGLBindProgram");	

	cgGLSetTextureParameter(xParam, xTexID);
	checkForCgError("cgGLSetTextureParameter");

	cgGLEnableTextureParameter(xParam);
	checkForCgError("cgGLEnableTextureParameter");

	glFinish();

	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);//write

	cgGLSetTextureParameter(yParam, yTexID[readTex]);
	checkForCgError("cgGLSetTextureParameter");
	cgGLEnableTextureParameter(yParam);
	checkForCgError("cgGLEnableTextureParameter");

	glPolygonMode(GL_FRONT,GL_FILL);
		glBegin(GL_QUADS);
			glTexCoord2f(0.0, 0.0); 
			glVertex2f(0.0, 0.0);
			glTexCoord2f(1.0, 0.0); 
			glVertex2f(texSize, 0.0);
			glTexCoord2f(1.0, 1.0); 
			glVertex2f(texSize, texSize);
			glTexCoord2f(0.0, 1.0); 
			glVertex2f(0.0, texSize);
		glEnd();

    checkFramebufferStatus();
}

bool checkFramebufferStatus() {
    GLenum status;
    status = cast(GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status) {
        case GL_FRAMEBUFFER_COMPLETE_EXT:
            return true;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			printf("Framebuffer incomplete, incomplete attachment\n");
            return false;
        case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
			printf("Unsupported framebuffer format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
			printf("Framebuffer incomplete, missing attachment\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			printf("Framebuffer incomplete, attached images must have same dimensions\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			printf("Framebuffer incomplete, attached images must have same format\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
			printf("Framebuffer incomplete, missing draw buffer\n");
            return false;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
			printf("Framebuffer incomplete, missing read buffer\n");
            return false;
    }
	return false;
}



class Gpgpu{
	mixin SingletonMix;

	void start(){

	}

	void genTerrain(inout ubyte* hMap, int size){
	init();
	screen();

	int N = size*size;
	texSize = cast(int)sqrt(cast(float)N/4.0);

    dataX = cast(float*)malloc(N * float.sizeof);
	dataY = cast(float*)malloc(N * float.sizeof);

	int sqrN = cast(int)sqrt(N);
	for (int i=0; i<N; i++) {
		dataX[i] = cast(float)(i % sqrN);
		dataY[i] = cast(float)(i/sqrN);
	}

	initFBO();

	createTextures();
	
	initCg();
	initCgGl();

	initMyCgProgram();
	writefln("Computation...");
	
	performComputation();

    float* data = cast(float*)malloc(N*float.sizeof);
    transferFromTexture(data);
	checkForCgError("transferFromTexture");

	    for (int i=0; i<N; i++){
			assert(data[i] <= 1.0);
			assert(data[i] >= -1.0);
			hMap[i] = cast(ubyte)(((data[i]+1)/2.0)*128.0);
		}
	}

	void computePi(){
		int N = 4;
		texSize = cast(int)sqrt(cast(float)N/4.0);
    	dataY = cast(float*)malloc(N * float.sizeof);
		for (int i=0; i<N; i++) {
			dataY[i] = cast(float)(0);
		}
		initFBO();
	    glGenTextures (2, yTexID);

	    setupTexture (yTexID[readTex]);
		transferToTexture(dataY,yTexID[readTex]);
	    setupTexture (yTexID[writeTex]);
		transferToTexture(dataY,yTexID[writeTex]);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		myPiFragmentProgram = 
			cgCreateProgramFromFile(
					myCgContext,
					CGenum.CG_SOURCE,
					toStringz(myFragmentProgramFileName),
					myCgFragmentProfile,
					toStringz("Pi"),
					null);
		checkForCgError("creating vertex program from file");
		cgGLLoadProgram(myPiFragmentProgram);
		checkForCgError("loading vertex program");
		writefln("Loaded.");
		yParam = cgGetNamedParameter(myCgFragmentProgram, toStringz("textureY"));

	    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, yTexID[writeTex], 0);//write
    	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, yTexID[readTex], 0);//read

		cgGLEnableProfile(myCgFragmentProfile);
		checkForCgError("cgGLEnableProfile");	

		cgGLBindProgram(myPiFragmentProgram);
		checkForCgError("cgGLBindProgram");	
		glFinish();

		glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);//write

		cgGLSetTextureParameter(yParam, yTexID[readTex]);
		checkForCgError("cgGLSetTextureParameter");
		cgGLEnableTextureParameter(yParam);
		checkForCgError("cgGLEnableTextureParameter");

		glPolygonMode(GL_FRONT,GL_FILL);
			glBegin(GL_QUADS);
			glTexCoord2f(0.0, 0.0); 
			glVertex2f(0.0, 0.0);
			glTexCoord2f(1.0, 0.0); 
			glVertex2f(texSize, 0.0);
			glTexCoord2f(1.0, 1.0); 
			glVertex2f(texSize, texSize);
			glTexCoord2f(0.0, 1.0); 
			glVertex2f(0.0, texSize);
		glEnd();

	    checkFramebufferStatus();

	    float* data = cast(float*)malloc(N*float.sizeof);
    	transferFromTexture(data);
		checkForCgError("transferFromTexture");

		writefln("Pi == ", data[0]);
	}

	void clean(){
	    glDeleteFramebuffersEXT(1,&fb);
    	glDeleteTextures(2,yTexID);
	    glDeleteTextures (1,&xTexID);
		cgGLDisableProfile(myCgFragmentProfile);
		cgDestroyContext(myCgContext);
		SDL_Quit();
	    DerelictGL.unload();
    	DerelictGLU.unload();
		DerelictSDL.unload();
	}
}
alias SingletonGetter!(Gpgpu) gpgpu;
